/*************************************************************************
	> File Name: judge.cpp
	> Author:ZhangKe 
	> Mail:ch.zhangke@gmail.com 
	> Created Time: 2017年02月08日 星期三 23时29分12秒
 ************************************************************************/

#include <iostream>
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <cstring>
#include <sys/stat.h>
#include <unistd.h>
#include <queue>
#include <string>
#include <sys/resource.h>
#include <sys/wait.h>
#include <mysql.h>
#include <my_global.h>

#define STRSIZE 1024
#define STD_MB 1048576
char sqladdress[STRSIZE];
int  sqlport;
char username[STRSIZE];
char passwd[STRSIZE];
char databasename[STRSIZE];
char workdir[STRSIZE];
int  clientNumber;
static MYSQL *conn;
static MYSQL_RES *res;
const char queryJobs[] = "select * from jobs where RESULT = 0";
const char sqlcmd0ToM[] = "update jobs set RESULT = 'M' where RESULT = '0'";
const char sqlcmdMTo1[] = "update jobs set RESULT = '1' where RESULT = 'M'";
const char sqlcmdQuery[] = "select * from jobs where RESULT = 'M'";

class aJob;
void configure(FILE *);
bool read_buf(char *buf, const char *key, char *value);
bool read_int(char *buf, const char *key, int *value);
bool create_folder(const char *basedir);
bool init_mysql();
int getJobsSql(std::queue<aJob> &);
void waitClient(pid_t *, int *);
int cmd_execute(const char *fmt, ...);

pid_t *pids;
void run_client(std::string ID, std::string PID, std::string Lang);

class aJob
{
private:
    std::string ID;
    std::string prbID;
    std::string lang;
public:
    aJob(std::string a, std::string b, std::string l) : ID(a), prbID(b), lang(l)
    {}
    std::string getID() const
    {
        return ID;
    }

    std::string getPrdId() const
    {
        return prbID;
    }

    std::string getLang() const
    {
        return lang;
    }
};

int main(int argc, char *argv[])    /* main argument is used to specific the path of configure file */
{
    FILE *conffile = NULL;
    if (argc >= 3)
    {
        printf("usage: ./judge conffilepath");
        return -1;
    }
    if (argc == 2) /* read the configuration from the path user specified */
    {
        if ((conffile = fopen(argv[1], "r")) == NULL)
        {
            printf("error when read configure from %s : %s\n", argv[1], strerror(errno));
            return -1;
        }
        configure(conffile);
    }
    else if (argc == 1) /* read the configuration from ./judge.conf */
    {
        if ((conffile = fopen("judge.conf", "r")) == NULL)
        {
            printf("no configure file\n");
            return -1;
        }
        configure(conffile);
    }
    printf("%s %d %s %s %s %s %d\n", sqladdress, sqlport, username, passwd, databasename, workdir,
          clientNumber);
    
    
    if (!create_folder(workdir))
    {
        printf("error when create work folder\n");
        return -1;
    }

    if (!init_mysql())
    {
        printf("error when connect mysql\n");
        return -1;
    }

    /* get jobs from mysql */
    pids = new pid_t[clientNumber];
    for (int i = 0; i < clientNumber; ++i)
        pids[i] = 0;
    std::queue<aJob> jobsqueue;
    int freeClient = clientNumber;
    int remainJobs = 0;
    int oldpid = getpid();
    
    while (1)    /* start get job from mysql and allocate son process to handle */
    {
        if (remainJobs == 0)  /* no judge job to handle, so query the mysql and get job */
        {
            remainJobs = getJobsSql(jobsqueue);
            
        }
        waitClient(pids, &freeClient);
        if (freeClient > 0)
        {
            /* allocate son process to handle */
            int realityRunClient = ((remainJobs < freeClient) ? remainJobs : freeClient);
            for (int k = 0; k < realityRunClient; ++k)
            {
                aJob newJob = jobsqueue.front();
                jobsqueue.pop();
                pid_t p;
                while ((p = fork()) < 0);
                if (p == 0)
                    run_client(newJob.getID(), newJob.getPrdId(), newJob.getLang());
                else if (p > 0)
                {
                    --remainJobs;
                    --freeClient;
                }
            }
        }
        sleep(1);
    }

    return 0;

}

int cmd_execute(const char *fmt, ...)
{
    char cmd[1024];

    int ret = 0;
    va_list ap;
    va_start(ap, fmt);
    vsprintf(cmd, fmt, ap);
    ret = system(cmd);
    va_end(ap);

    return ret;
}
void run_client(std::string ID, std::string PID, std::string Lang)
{
    struct rlimit LIM;
	LIM.rlim_max = 800;
	LIM.rlim_cur = 800;
	setrlimit(RLIMIT_CPU, &LIM);        //CPU时间的最大量值（秒）

	LIM.rlim_max = 80 * STD_MB;
	LIM.rlim_cur = 80 * STD_MB;
	setrlimit(RLIMIT_FSIZE, &LIM);      //可以创建文件的最大字节长度

	LIM.rlim_max = STD_MB << 11;
	LIM.rlim_cur = STD_MB << 11;
	setrlimit(RLIMIT_AS, &LIM);         //进程总的可用存储空间的最大长度（字节）

	LIM.rlim_cur = LIM.rlim_max = 200;

    std::string judge_client("judge_client");
    std::string judge_clientFileName = workdir + judge_client;
    for (int i = 0; i < clientNumber; ++i)
    {
        if (pids[i] == 0)
        {
            /* cp code file */
            cmd_execute("/bin/cp %sCode/%s.* %sJudging/%d/%s.cpp", workdir, ID.c_str(), workdir, (i+1), ID.c_str());
            pids[i] = getpid();
            for (int k = 0; k < clientNumber; ++k)
                std::cout << "another: " << pids[k];
            std::cout << std::endl;
            printf("now running pid: %d\n", getpid());
            char serial[4];
            sprintf(serial, "%d", i + 1);
            int returnValue = execl(judge_clientFileName.c_str(), judge_clientFileName.c_str(), PID.c_str(), ID.c_str(), std::string(workdir).c_str(), serial, Lang.c_str(), sqladdress, databasename, username, passwd, (char *)0);
            if (returnValue < 0)
                std::cout << "execl error" << std::endl;
            exit(returnValue);
        }
    }
}
void waitClient(pid_t *pids, int *freeClient)
{
    int temp = clientNumber - *freeClient;
    for (int i = 0; i < temp; ++i)
    {
        pid_t pid;
        if ((pid = waitpid(-1, NULL, WNOHANG)) != 0)
        {
            ++(*freeClient);
            for (int k = 0; k < clientNumber; ++k)
            {
                if (pids[k] == pid)
                {
                    pids[k] = 0;
                    break;
                }
            }
        }
    }
}
void configure(FILE* file)
{
    char buf[STRSIZE];
    int length;
    if(file != NULL)
    {
        while(fgets(buf, STRSIZE - 1, file))
        {
            length = strlen(buf);
            buf[length - 1] = '\0';     //get rid of '\A'
            read_buf(buf, "sqladdress", sqladdress);
            read_int(buf, "sqlport", &sqlport);
            read_buf(buf, "username", username);
            read_buf(buf, "passwd", passwd);
            read_buf(buf, "databasename", databasename);
            read_buf(buf, "workdir", workdir);
            read_int(buf, "clientNumber", &clientNumber);
        }
        if (clientNumber <= 0)
            clientNumber = 4;
        if (clientNumber >= 1000)
            clientNumber = 999;
    }
}

bool read_buf(char *buf, const char *key, char *value)
{
    if (strncmp(key, buf, strlen(key)) == 0)
    {
        strcpy(value, buf + strlen(key) + 1);
        return true;
    }
    return false;
}

bool read_int(char *buf, const char *key, int *value)
{
    char temp[STRSIZE];
    /*
    if (strncmp(key, buf, strlen(key) == 0))
    {
        printf("can print temp\n");
        strcpy(temp, buf + strlen(key) + 1);
        printf("%s\n", temp);
        *value = atoi(temp);
        return true;
    }
    */
    if (read_buf(buf, key, temp))
    {
        sscanf(temp, "%d", value);
        return true;
    }
    return false;
}

bool create_folder(const char *basedir)
{
    int basedirfd;
    int judgingfd;
    int i;
    char end[4];
    if ((basedirfd = open(basedir, O_DIRECTORY | O_RDONLY )) == -1)
    {
        printf("error when open dir \"%s\": %s\n", basedir, strerror(errno));
        return false;
    }
    if (fchdir(basedirfd) != 0)
    {
        printf("error when chdir to %s: %s\n", workdir, strerror(errno));
        return -1;
    }
    if ((judgingfd = openat(basedirfd, "Judging", O_DIRECTORY | O_RDONLY)) == -1)
    {
        printf("error when open dir \"judging\": %s\n", strerror(errno));
        return false;
    }
    for (i = 0; i < clientNumber; ++i)   /* create judging folder */
    {
        sprintf(end, "%d", i + 1);
        if (mkdirat(judgingfd, end, 0770) == -1)   /* drwxrwx--- */
        {
            printf("error when create judging folder: %s\n", strerror(errno));
            return false;
        }
    }
    return true;
}

bool init_mysql()
{
    const unsigned int timeout = 5; /* set connect timeout 5 second */
    if (conn == NULL)
    {
        conn = mysql_init(NULL);
        if (conn == NULL)
        {
            printf("Error %u: %s\n", mysql_errno(conn), mysql_error(conn));
            return false;
        }
        mysql_options(conn, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);
        if (mysql_real_connect(conn, sqladdress, username, passwd, databasename, sqlport, NULL, 0) == 0)
        {
            printf("Error %u: %s\n", mysql_errno(conn), mysql_error(conn));
            return false;
        }
        return true;
    }
    return false;
}

int getJobsSql(std::queue<aJob> &jobsqueue)
{
    mysql_query(conn, sqlcmd0ToM);
    
    do
    {
        sleep(1);
    } while(mysql_real_query(conn, sqlcmdQuery, strlen(sqlcmdQuery)));

    MYSQL_ROW row;
    res = mysql_store_result(conn);
    while(row = mysql_fetch_row(res))
    {
        jobsqueue.emplace(row[0], row[2], row[4]);
    }
    mysql_query(conn, sqlcmdMTo1);
    return jobsqueue.size();
}
